#include<iostream>
#include<unistd.h>
#include<cstdio>
#include<string>
#include<cstring>
#include<stdlib.h>
#include<cassert> 
// 等价于<assert.h> c/c++混编的时候，最好是用前者的方式
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
using namespace std;

//使用pipe函数创建管道，实现父子进程之间的进程间通信
int main()
{
    //第一步：创建管道文件，并返回读写端
    int fd[2];
    int n = pipe(fd);
    assert(n == 0); // pipe不返回0就是调用失败了

    // 由于0、1、2,默认是标准输入输出，因此fd获取到的文件描述符最小也是3和4
    // cout << fd[0] << endl; // 读端
    // cout << fd[1] << endl; // 写端

    //第二步 创建子进程【子进程写入】
    pid_t id = fork();
    assert(id != -1);
    if(id == 0)
    {
        // child
        // 子进程向管道写入数据
        close(fd[0]); //关闭子进程读端
        //通信代码
        int cnt = 0;
        //不断向父进程发送信息
        while(1)
        {
            char buffer[1024];
            snprintf(buffer, sizeof buffer, "child 第%d次说：hello, 父进程", cnt++);
            // 注意：如果写端写满了，那么也会阻塞写端所在的进程
            write(fd[1], buffer, strlen(buffer)); //向管道文件写入数据
            sleep(1); // 每隔1s，就往管道输入信息，不然跳的太快了
            //sleep(5); //用于验证， 管道中没有数据，但是父进程读端在读取管道数据，会发生什么
            if(cnt == 5)
                break;
        }
        
        cout << "子进程将写端关闭" << endl;
        close(fd[1]); //不用了就关掉
        exit(0);
    }

    //parent【父进程读取】
    close(fd[1]); //关闭父进程写端
    //不断接收子进程发来的信息。/
    while(1)
    {
        char buffer[1024];
        //注意：如果管道中没有数据，但是读端在读取管道数据，那么就会造成当前正在读取的进程堵塞
        //cout << "读取前输出:parent准备读取管道数据\n";
        ssize_t s = read(fd[0], buffer, sizeof(buffer) - 1); //作为一个字符串来读取，始终留一个位置放'/0'
        //注意：read如果读取到管道文件已经关闭，就会调用失败，返回0
        if(s > 0)
        {
            buffer[s] = 0;
        }
        else if(s == 0)
        {   
            cout << "read return："  << s << endl;
            break;
        }
        //cout << "读取后输出:parent已经读取完管道数据\n";
        cout << "父进程接收：" << buffer << endl;
        // 父进程不需要sleep(1)，只要子进程往管道输送信息，父进程收到到就输出到屏幕上
        //break; // 测试，读端关闭，写端继续输入后的现象
    }

    close(fd[0]); // 不用了就关掉
    
    int status = 0;
    n = waitpid(id, &status, 0); // 进程等待
    assert(n != -1);
    // 将子进程退出信息打印到屏幕上
    cout << "sign number: " << ((status) & 0x7F) << "  exit code: " << ((status >> 8) & 0xFF) << endl;

    cout << "本次进程间通信已经结束" << endl;

    return 0;
}
